#include <stdio.h>

#include <libusb-1.0/libusb.h>

typedef struct kbd_device_info {
    libusb_device *kbd_device;
    uint8_t endpoint_address;
    uint8_t interface_num;
} kbd_device_info;

#define USB_INTERFACE_SUBCLASS_BOOT     1
#define USB_INTERFACE_PROTOCOL_KEYBOARD 1

int32_t get_in_endpoint(const struct libusb_interface_descriptor *intf_desc, uint8_t *ep_addr)
{
    for (int32_t idx = 0; idx < intf_desc->bNumEndpoints; ++idx) {
        const struct libusb_endpoint_descriptor *ep_desc = &intf_desc->endpoint[idx];
        if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK == LIBUSB_ENDPOINT_IN) {
            *ep_addr = ep_desc->bEndpointAddress;
            return 0;
        }
    }
    printf("no keyboard in endpoint\n");
    return -1;
}

int32_t get_kbd_endpoint_addr(const struct libusb_config_descriptor *cfg_desc, uint8_t *ep_addr, uint8_t *interface_num)
{
    const struct libusb_interface *interface;
    for (uint32_t intf_idx = 0; intf_idx < cfg_desc->bNumInterfaces; intf_idx++) {
        interface = &cfg_desc->interface[intf_idx];
        for (uint32_t altsetting_idx = 0; altsetting_idx < interface->num_altsetting; ++altsetting_idx) {
            const struct libusb_interface_descriptor *intf_desc = &interface->altsetting[altsetting_idx];
            if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID &&
                intf_desc->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT &&
                intf_desc->bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD) {
                *interface_num = intf_desc->bInterfaceNumber;
                return get_in_endpoint(intf_desc, ep_addr);
            }
        }
    }
    printf("no keyboard configuration\n");
    return -1;
}

int32_t get_kbd_device_info(libusb_device **devs, kbd_device_info *info)
{
    uint32_t i = 0;
    libusb_device *dev;
    while ((dev = devs[i++]) != NULL) {
        struct libusb_device_descriptor dev_desc;
        int r = libusb_get_device_descriptor(dev, &dev_desc);
        if (r < 0) {
            fprintf(stderr, "failed to get device descriptor\n");
            continue;
        }

        for (uint32_t cfgIdx = 0; cfgIdx < dev_desc.bNumConfigurations; ++cfgIdx) {
            struct libusb_config_descriptor *cfg_desc;
            r = libusb_get_config_descriptor(dev, cfgIdx, &cfg_desc);
            if (r != LIBUSB_SUCCESS) {
                fprintf(stderr, "failed to get config descriptor %d\n", r);
                continue;
            }
            uint8_t ep_addr, interface_num;
            r = get_kbd_endpoint_addr(cfg_desc, &ep_addr, &interface_num);
            if (r < 0) {
                fprintf(stderr, "failed to get endpoint address\n");
                continue;
            }

            info->kbd_device = dev;
            info->endpoint_address = ep_addr;
            info->interface_num = interface_num;
            return 0;
        }
    }
    return -1;
}

int main(void)
{
    libusb_device **devs;
    int r;
    ssize_t cnt;

    r = libusb_init(NULL);
    if (r < 0)
        return r;

    cnt = libusb_get_device_list(NULL, &devs);
    if (cnt < 0) {
        libusb_exit(NULL);
        return (int)cnt;
    }

    kbd_device_info info;
    if (get_kbd_device_info(devs, &info) != 0) {
        printf("get device info failed\n");
        goto ERR;
    }

    libusb_device_handle *dev_handle = NULL;
    r = libusb_open(info.kbd_device, &dev_handle);
    if (r != 0) {
        printf("open device failed %d\n", r);
        goto ERR;
    }

    r = libusb_set_auto_detach_kernel_driver(dev_handle, 1);
    if (r != 0) {
        printf("set auto detach failed %d\n", r);
        goto ERR;
    }

    r = libusb_claim_interface(dev_handle, info.interface_num);
    if (r != 0) {
        printf("claim interface failed %d\n", r);
        goto ERR;
    }

    unsigned char data[8] = {0};
    int act_len = 0;
    while (1) {
        r = libusb_interrupt_transfer(dev_handle, info.endpoint_address, data, 8, &act_len, 0);
        if (r != 0) {
            printf("interrupt transfer failed %d\n", r);
            goto ERR;
        }
        printf("act len :%d, data: ", act_len);

        for (int i = 0; i < 8; ++i) {
            printf("%u ", data[i]);
        }
        printf("\n");
    }
ERR:
    libusb_free_device_list(devs, 1);
    libusb_exit(NULL);
    return 0;
}
